/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.alf.scriptrunner;

import com.alf.scriptrunner.exceptions.DelimiterExpectedException;
import com.alf.scriptrunner.exceptions.RuntimeIOException;
import com.alf.scriptrunner.exceptions.EOFUnexpectedException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;

/**
 * Divide SQL Statements in the script
 *
 * @author jmnovelov
 */
public class CharProcessor implements Iterable<String> {

    public static final int[][] STATES = {
        {0,3,1,1,1,1,1,1,0,15,1},
        {1,1,1,1,1,1,1,1,1,16,1},
        {0,0,0,0,0,0,0,0,0,0,0},
        {1,1,1,1,1,1,4,1,1,16,1},
        {1,1,5,1,1,1,1,1,1,16,1},
        {1,1,1,1,6,1,1,1,1,16,1},
        {1,1,1,7,1,1,1,1,1,16,1},
        {1,1,1,1,8,1,1,1,1,16,1},
        {1,1,1,1,1,9,1,1,1,16,1},
        {1,1,1,1,1,1,10,1,1,16,1},
        {1,1,1,1,1,1,1,11,1,16,1},
        {14,1,1,1,1,1,1,1,1,16,1},
        {12,12,12,12,12,12,12,12,13,16,12},
        {0,0,0,0,0,0,0,0,0,0,0},
        {14,12,12,12,12,12,12,12,17,16,12},
        {0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0}
    };

    public static final int SQL_INSTRUCTION = 2;

    public static final int NEW_DELIMITER = 13;

    public static final int END_OF_FILE = 15;

    public static final int EOF_UNEXPECTED = 16;

    public static final int DELIMITER_EXPECTED = 17;

    public static final int SPACE_BLANK = 0;

    public static final int D = 1;

    public static final int L = 2;

    public static final int M = 3;

    public static final int I = 4;

    public static final int T = 5;

    public static final int E = 6;

    public static final int R = 7;

    public static final int EOL = 8;

    public static final int EOF = 9;

    public static final int OTHER = 10;

    private InputStream input;

    private String delimiter;

    /**
     * Creates an instance with the input stream and ';' as a delimiter
     * @param input
     */
    public CharProcessor(InputStream input) {
        this.input = input;
        this.delimiter = ";";
    }

    /**
     * Creates an instance with the input stream and a delimiter
     * @param input
     * @param delimiter
     */
    public CharProcessor(InputStream input, String delimiter) {
        this.input = input;
        this.delimiter = delimiter;
    }

    /**
     * create an Token iterator
     * @return
     */
    @Override
    public Iterator<String> iterator() {
        try {
            return new TokenIterator(input);
        } catch (IOException ex) {
            throw new RuntimeIOException(ex);
        }
    }

    /**
     * This class reads the InputStream and gives tokens
     */
    private class TokenIterator implements Iterator<String> {

        private static final String IOEXCEPTION_MESSAGE = 
                "IOException was thrown processing line: ";

        private BufferedReader reader;
        
        private String line;

        private int lineNumber;

        private int column;

        private String sqlInstruction;

        public TokenIterator(InputStream input) throws IOException {
            reader = new BufferedReader(new InputStreamReader(input));
            lineNumber = 1;
            column = 0;
            line = reader.readLine();
        }

        @Override
        public boolean hasNext() {
            int state = 0;
            StringBuffer buffer = new StringBuffer();
            Character character;

            try {
                while(true) {
                    switch (state = 
                            STATES[state][getColumn(character = read())]) {

                        case NEW_DELIMITER:
                            delimiter = buffer.toString().trim().substring(9)
                                    .trim();
                            state = 0;
                            buffer = new StringBuffer();
                            break;

                        case END_OF_FILE:
                            return false;

                        case EOF_UNEXPECTED:
                            throw new EOFUnexpectedException();

                        case DELIMITER_EXPECTED:
                            throw new DelimiterExpectedException();

                        default:
                            buffer.append(character);
                            int bufferLength = buffer.length();
                            int length = delimiter.length();

                            if (bufferLength >= length) {
                                boolean ends = true;

                                for (int i = 0; i < length; ++i) {
                                    if (buffer.charAt(bufferLength - length + i)
                                            != delimiter.charAt(i)) {
                                        ends = false;
                                        break;
                                    }
                                }

                                if (ends) {
                                    sqlInstruction = buffer.substring(0,
                                            bufferLength - length);
                                    return true;
                                }
                            }
                    }
                }
            } catch (IOException ex) {
                throw new RuntimeIOException(IOEXCEPTION_MESSAGE + line, ex);
            }
        }

        @Override
        public String next() {
            return sqlInstruction;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        private int getColumn(Character character) {
            if (character != null) {
                char asChar = character.charValue();

                switch (asChar) {
                    case '\n':
                        return EOL;
                    case 'D':
                    case 'd':
                        return D;
                    case 'L':
                    case 'l':
                        return L;
                    case 'M':
                    case 'm':
                        return M;
                    case 'I':
                    case 'i':
                        return I;
                    case 'T':
                    case 't':
                        return T;
                    case 'E':
                    case 'e':
                        return E;
                    case 'R':
                    case 'r':
                        return R;
                    default:
                        if (Character.isWhitespace(asChar)) {
                            return SPACE_BLANK;
                        }

                        return OTHER;
                }
            }

            return EOF;
        }

        /**
         * @return null means EOF, \n end of line
         * @throws IOException
         */
        private Character read() throws IOException {
            if (line != null) {
                if (column < line.length()) {
                    int toRead = column;
                    ++column;
                    return line.charAt(toRead);

                } else {
                    line = reader.readLine();

                    if (line != null) {
                        ++lineNumber;
                        column = 0;
                        return '\n';
                    }
                }
            }

            reader.close();
            return null;
        }
    }
}
